package com.kgis.kp.dao.impl;

import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.orm.ibatis.SqlMapClientCallback;
import org.springframework.orm.ibatis.SqlMapClientTemplate;

import com.ibatis.sqlmap.client.SqlMapExecutor;
import com.kgis.kp.common.util.Assertion;
import com.kgis.kp.common.vo.Ordering;
import com.kgis.kp.common.vo.Pagination;
import com.kgis.kp.dao.IbatisDataAccessObject;
import com.kgis.kp.dao.vo.PaginationMapParameter;
import com.kgis.kp.model.Model;

/**
 * 
 * @author zhxiaomiao
 * 
 */
public class IbatisDataAccessObjectImpl extends AbstractDataAccessObjectImpl implements IbatisDataAccessObject {

	private static final transient Log log = LogFactory.getLog(IbatisDataAccessObjectImpl.class);

	private SQLExceptionTranslator exceptionTranslator;

	private int maxRows = MAX_ROWS;

	private SqlMapClientTemplate sqlMapClientTemplate;

	/** sequence */
	public long sequence(String namespace) {
		try {
			Long id = (Long) this.getSqlMapClientTemplate().queryForObject(fullname(namespace, STATEMENT_SEQUENCE));
			return id.longValue();
		} catch (Throwable e) {
			String msg = "namespace=" + namespace;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	/** create */
	public <T> T execute(SqlMapClientCallback<T> sqlMapClientCallback) {
		try {
			return this.getSqlMapClientTemplate().execute(sqlMapClientCallback);
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	/** create */
	public <T> T create(final String namespace, final T model) {
		Assertion.notNull(model, "<T> model not found");
		try {
			return this.execute(new SqlMapClientCallback<T>() {
				public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
					executor.insert(fullname(namespace, STATEMENT_CREATE), model);
					return model;
				}
			});
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", model=" + model.getClass();
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	public <T> int create(final String namespace, final List<T> models) {
		Assertion.notNull(models, "List<T> model not found!");
		try {
			return this.execute(new SqlMapClientCallback<Integer>() {
				public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
					executor.startBatch();
					for (T model : models) {
						executor.insert(fullname(namespace, STATEMENT_CREATE), model);
					}
					return new Integer(executor.executeBatch());
				}
			});
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", models=" + models;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	/** retrieve/read/find */
	@SuppressWarnings("unchecked")
	public <T> T findById(final String namespace, long id) {
		try {
			return (T) this.getSqlMapClientTemplate().queryForObject(fullname(namespace, STATEMENT_FIND_BY_ID), new Long(id));
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", id=" + id;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	@SuppressWarnings("unchecked")
	public <T> T findOne(final String namespace, final String statement, final Object parameter) {
		try {
			return (T) this.getSqlMapClientTemplate().queryForObject(fullname(namespace, statement), parameter);
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", statement=" + statement + ", parameter=" + parameter;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	@SuppressWarnings("unchecked")
	public <T> T findOne(final String namespace, final String statement, final Map<String, Object> parameters) {
		try {
			return (T) this.getSqlMapClientTemplate().queryForObject(fullname(namespace, statement), parameters);
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", statement=" + statement + ", parameters=" + parameters;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> find(final String fullName, final Map<String, Object> parameters, final Ordering ordering) {
		try {
			return this.execute(new SqlMapClientCallback<List<T>>() {
				public List<T> doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
					List<T> list = (List<T>) executor.queryForList(fullName, new PaginationMapParameter(parameters, ordering, null));
					return escapeEmpty(list);
				}
			});
		} catch (Throwable e) {
			String msg = "fullName=" + fullName + ", parameters=" + parameters + ", ordering=" + ordering;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	// public <T> List<T> find(final String fullName, final CriteriaParameter
	// criteriaParameter, final Ordering ordering) {
	// Map<String, Object> parameters = null;
	// if (null != criteriaParameter) {
	// parameters = criteriaParameter.toParameters();
	// }
	// return find(fullName, parameters, ordering);
	// }

	public <T> List<T> find(String fullName, Map<String, Object> parameters) {
		return find(fullName, parameters, null);
	}

	public <T> List<T> find(final String namespace, final String statement, final Map<String, Object> parameters, final Ordering ordering) {
		return this.find(fullname(namespace, statement), parameters, ordering);
	}

	public <T> List<T> find(final String namespace, final String statement, final Map<String, Object> parameters) {
		return this.find(fullname(namespace, statement), parameters, null);
	}

	// using jdbcTemplateDataAccessObject instead
	/*
	 * public List findRawMap(final String namespace, final String statement,
	 * final Map<String, Object> parameters, final Ordering ordering){ return
	 * find(fullname(namespace, statement), parameters, ordering); }
	 */

	@SuppressWarnings("unchecked")
	public <T> Pagination<T> findPagination(final String fullName, final Map<String, Object> parameters, final Ordering ordering, final Pagination<T> pagination) {
		// Assertion.notNull(pagination, "Pagination<T> pagination not found");

		try {
			return this.execute(new SqlMapClientCallback<Pagination<T>>() {
				public Pagination<T> doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
					Pagination<T> safePagination = pagination;
					if (null == safePagination) {
						safePagination = new Pagination<T>();
					}
					if (safePagination.isCounted()) {
						String countName = count(fullName);
						safePagination.setCount(count(countName, parameters));
					}
					PaginationMapParameter paginationMapParameter = null;
					if (parameters != null && parameters.containsKey(SKIP_RESULTS) && parameters.containsKey(MAX_RESULTS)) {
						Number skipResult = (Number) parameters.get(SKIP_RESULTS);
						Number maxResult = (Number) parameters.get(MAX_RESULTS);
						paginationMapParameter = new PaginationMapParameter(parameters, ordering, skipResult.intValue(), maxResult.intValue());
					} else {
						paginationMapParameter = new PaginationMapParameter(parameters, ordering, safePagination);
					}
					List<T> list = (List<T>) executor.queryForList(fullName, paginationMapParameter);
					safePagination.setItems(escapeEmpty(list));
					return safePagination;
				}
			});
		} catch (Throwable e) {
			String msg = "fullName=" + fullName + ", parameters=" + parameters + ", ordering=" + ordering + ", pagination=" + pagination;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	// public <T> Pagination<T> findPagination(final String fullName, final
	// CriteriaParameter criteriaParameter, final Ordering ordering, final
	// Pagination<T> pagination) {
	// Map<String, Object> parameters = null;
	// if (null != criteriaParameter) {
	// parameters = criteriaParameter.toParameters();
	// }
	// return findPagination(fullName, parameters, ordering, pagination);
	//
	// }

	public <T> Pagination<T> findPagination(final String namespace, final String statement, final Map<String, Object> parameters, final Ordering ordering, final Pagination<T> pagination) {
		return this.findPagination(fullname(namespace, statement), parameters, ordering, pagination);
	}

	public <T> List<T> findAll(final String namespace) {
		return this.findAll(namespace, null);
	}

	public <T> List<T> findAll(String namespace, Ordering ordering) {
		return this.find(fullname(namespace, STATEMENT_FIND_ALL), (Map<String, Object>) null, ordering);
	}

	/** count */
	public int count(final String fullName, final Map<String, Object> parameters) {
		try {
			return Integer.parseInt(String.valueOf(this.getSqlMapClientTemplate().queryForObject(fullName, parameters)));
		} catch (Throwable e) {
			String msg = "fullName=" + fullName + ", parameters=" + parameters;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	public int count(final String namespace, final String statement, final Map<String, Object> parameters) {
		return this.count(fullname(namespace, statement), parameters);
	}

	/** also see <POJOs in Action> */
	/** update */
	public <T> T update(final String namespace, final T model, final boolean optimisticLockStep) {
		Assertion.notNull(model, "T model not found");
		try {
			return this.execute(new SqlMapClientCallback<T>() {
				public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
					int numberOfRowsEffected = executor.update(fullname(namespace, STATEMENT_UPDATE), model);
					if (numberOfRowsEffected == 0) {
						throw new OptimisticLockingFailureException(String.valueOf(model));
					}
					return model;
				}
			});
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", model=" + model.getClass() + ", optimisticLockStep=" + optimisticLockStep;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	public <T> T update(final String namespace, final T model) {

		return update(namespace, model, true);
	}

	public <T> int update(final String namespace, final List<T> models) {
		Assertion.notNull(models, "List<T> models not found");

		try {
			return toPrimitive(this.execute(new SqlMapClientCallback<Integer>() {
				public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
					executor.startBatch();
//					Model model = null;
//					for (int i = 0; i < models.size(); i++) {
//						model = (Model) models.get(i);
////						executor.update(fullname(model.namespace(), STATEMENT_UPDATE), model);
//					}
//					model = null;
					return new Integer(executor.executeBatch());
				}
			}));
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", models=" + models;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	public int update(final String namespace, final String statement, final Object parameter) {
		Assertion.notNull(parameter, "Object parameter not found");

		try {
			return this.getSqlMapClientTemplate().update(fullname(namespace, statement), parameter);
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", statement=" + statement + ", parameter=" + parameter;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	/** remove */
	public <T> T remove(final String namespace, final T model) {
		Assertion.notNull(model, "T model not found");
		try {
			return this.execute(new SqlMapClientCallback<T>() {
				public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
					int numberOfRowsEffected = executor.delete(fullname(namespace, STATEMENT_DELETE), model);
					if (numberOfRowsEffected == 0) {
						throw new OptimisticLockingFailureException(String.valueOf(model));
					}
					return model;
				}
			});
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", model=" + model.getClass();
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	public int remove(final String namespace, final String statement, final Object parameter) {
		Assertion.notNull(parameter, "Object parameter not found");
		try {
			return this.getSqlMapClientTemplate().delete(fullname(namespace, statement), parameter);
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", statement=" + statement + ", parameter=" + parameter;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	public <T> int remove(final String namespace, final List<T> models) {
		Assertion.notNull(models, "<<List models>> not found!");
		try {
			return toPrimitive(this.execute(new SqlMapClientCallback<Integer>() {
				public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
					executor.startBatch();
					Model model = null;
					for (int i = 0; i < models.size(); i++) {
						model = (Model) models.get(i);
						executor.delete(fullname(namespace, STATEMENT_DELETE), model);
					}
					model = null;
					return new Integer(executor.executeBatch());
				}
			}));
		} catch (Throwable e) {
			String msg = "namespace=" + namespace + ", models=" + models;
			log.error(msg, e);
			throw new RuntimeException(msg, e);
		}
	}

	// setter/getter
	/**
	 * @return the sqlMapClientTemplate
	 */
	public SqlMapClientTemplate getSqlMapClientTemplate() {
		return sqlMapClientTemplate;
	}

	/**
	 * @param sqlMapClientTemplate
	 *            the sqlMapClientTemplate to set
	 */
	public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) {
		this.sqlMapClientTemplate = sqlMapClientTemplate;
	}

	/**
	 * @return the maxRows
	 */
	public int getMaxRows() {
		return maxRows;
	}

	/**
	 * @param maxRows
	 *            the maxRows to set
	 */
	public void setMaxRows(int maxRows) {
		this.maxRows = maxRows;
	}

	/**
	 * @return the exceptionTranslator
	 */
	public SQLExceptionTranslator getExceptionTranslator() {
		return exceptionTranslator;
	}

	/**
	 * @param exceptionTranslator
	 *            the exceptionTranslator to set
	 */
	public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
		this.exceptionTranslator = exceptionTranslator;
	}

}
